package com.cn.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cn.DTO.LoginFormDTO;
import com.cn.DTO.RedisData;
import com.cn.utils.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redis")
public class RedisController {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //token
    private final String token = "token";
    //商品库存
    private final String RedisShop="cache:shop:";
    //枷锁
    private final String LockShop="lock:shop:";
    private static final ExecutorService CACHE = Executors.newFixedThreadPool(10);
    private static final String KEY_PREFIX = "lock";
    private static final String ID_PREFIX  = UUID.randomUUID().toString()+"-";
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }
    public void unlock(){
        //调用lun脚本方式
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX),
               ID_PREFIX +Thread.currentThread().getId()
        );
    }
    private boolean tryLockTest(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        //获取锁的方式
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX, threadId, 10, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }
    @GetMapping("/testLock")
    public String testLock(){
        tryLockTest(10l);
        unlock();
        return "成功";
    }
    @PostMapping("/login")
    public String login(LoginFormDTO loginFormDTO, HttpSession session){
        //1.校验手机号码
        String phone = loginFormDTO.getPhone();
        //2.校验验证码
        String cachCode = stringRedisTemplate.opsForValue().get(phone);
        String code = loginFormDTO.getCode();
        //3.判断用户是否存在
        //4.登录
        String token = UUID.randomUUID().toString();
        loginFormDTO.setPassword("234");
        loginFormDTO.setUsername("345");
        loginFormDTO.setCode("sdf");
        Map<String, Object> map = BeanUtil.beanToMap(loginFormDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())
        );
        String tokenKey = "token"+token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,map);
        stringRedisTemplate.expire(tokenKey,10l, TimeUnit.MINUTES);
        UserHolder.set(code);
        return token;
    }
    @GetMapping("/sendCode")
    public String sendCode(String phone,HttpSession session){
        session.setAttribute("code","234");
        return "成功";
    }
    @GetMapping("/test")
    public String test(){
        return "成功";
    }
    @GetMapping("/shopById")
    public Object shopById(Integer id){
        String key = "cache:shop:"+id;
        String shopJSON = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJSON)){
            LoginFormDTO shop = JSONUtil.toBean(shopJSON, LoginFormDTO.class);
            return shop;
        }

        //判断是否为空
        if (shopJSON!=null){
            return null;
        }
        //开始获取缓存
        String lockKey = "lock:shop:"+id;
        LoginFormDTO loginFormDTO = null;
        try {
            boolean isLock = tryLock(lockKey);
            //判断是否获取成功
            if (!isLock){
                //失败进行休眠处理
                Thread.sleep(10);
                shopById(id);
            }
            //成功根据id查询
             loginFormDTO = new LoginFormDTO();
            loginFormDTO.setCode("234");
            loginFormDTO.setPhone("234324");
            loginFormDTO.setUsername("234");
            loginFormDTO.setPassword("23423");
            //不存在返回错误
            if (loginFormDTO==null){
                return "数据库不存在";
            }
            //将值写入
            stringRedisTemplate.opsForValue().set("cache:shop:"+id,JSONUtil.toJsonStr(lockKey),10l,TimeUnit.SECONDS);
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            unlock(lockKey);
        }
        return loginFormDTO;
    }
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
    @PutMapping("/updateById")
    public Object updateById(LoginFormDTO loginFormDTO ){
        System.out.println("修改数据库操作");
        //删除缓存的方式
        stringRedisTemplate.delete("cache:shop:"+loginFormDTO.getCode());
        return "删除成功";
    }
    public Object queryWithLogicalExpire(Long id){
        String key = RedisShop+id;
        //1.从redis中查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
       if (StrUtil.isBlank(json)){
           return null;
       }
        //3.存在直接返回
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        //4.命中获取数据
        LoginFormDTO loginFormDTO = JSONUtil.toBean((JSONObject) redisData.getData(), LoginFormDTO.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断数据是否过期
        if (expireTime.isAfter(LocalDateTime.now())){
            //6.没有过期直接返回
            return loginFormDTO;
        }
        //7.过期查询数据库
        String lockKey = LockShop+id;
        boolean isLock = tryLock(lockKey);
        if (isLock){
            CACHE.submit(()->{
               try {
                   //查询数控
               }catch (Exception e){
                   throw new RuntimeException(e);
               }finally {
                   unlock(lockKey);
               }
            });

        }
        return json;
    }
}
